/*
 * Copyright 2012, Serge V. Izmaylov
 * Released under GPL Version 2 license.
 */

function JSONSSError(code, message, data) {
    this.code = code;
    this.message = message;
    this.data = (typeof(data) == "undefined") ? null : data;
    return this;
}

function JSONSSProxy(intf, url) {
    this.serviceUrl = url;
    this.lastRequest = "";
    this.lastRequestHeaders = "";
    this.lastStatusCode = "";
    this.lastResponse = "";
    this.lastResponseHeaders = "";
    this._members = new Array();
    this._buildInterface(intf);
    return this;
}

JSONSSProxy.version = "1.0";

JSONSSProxy.JSONSS_SERVICE = 1;
JSONSSProxy.JSONSS_METHOD = 2;
JSONSSProxy.JSONSS_PROPERTY = 3;
JSONSSProxy.JSONSS_EVENT = 4;
JSONSSProxy.JSONSS_INTERFACE = 5;
JSONSSProxy.JSONSS_FAULT = 6;
JSONSSProxy.JSONSS_RESPONSE = 7;

JSONSSProxy.fromUrl = function (source, async, callback) {
    if ((source == null) || (source == ""))
        throw new JSONSSError(501, "Bad JSONSS source");
    var cache = JSONSSProxy._cache[source];
    if (cache != null) {
        if (callback != null)
            callback(cache);
        if (!async)
            return cache;
    } else {
        var xhr = JSONSSProxy._getXmlHttpRequest();
        xhr.open("GET", source, async?true:false);
        if(async) {
            xhr.onreadystatechange = function() {
                if(xhr.readyState == 4)
                    JSONSSProxy._onGotInterface(async, callback, xhr, source);
            }
        }
        try {
            xhr.send(null);
        } catch (x) {
            if (async)
                if (callback != null)
                    callback(new JSONSSError(501, "Can't get JSONSS interface info"));
        }
        if (!async) {
            if (xhr.readyState == 4)
                return JSONSSProxy._onGotInterface(async, callback, xhr, source);
            else
                throw new JSONSSError(501, "Can't get JSONSS interface info");
        }
    }
}

JSONSSProxy.fromInterface = function (intf, url) {
    var res = new JSONSSProxy(intf, url);
    return res;
}

JSONSSProxy.fromText = function (txt, url) {
    return JSONSSProxy.fromInterface(JSON.parse(txt), url);
}


JSONSSProxy._onGotInterface = function (async, callback, xhr, source) {
    var res;
    if (xhr.readyState != 4) {
        res = new JSONSSError(501, "Can't get JSONSS interface info");
    } else {
        try {
            res = JSONSSProxy.fromText(xhr.responseText, source);
            JSONSSProxy._cache[res.serviceUrl] = res;
        } catch (x) {
            if (x instanceof JSONSSError)
                res = x;
            else
                res = new JSONSSError(501, "Interface parsing error: " + x.toString());
        }
    }
    if (async) {
        if (callback != null)
            callback(res);
    } else {
        if (res instanceof JSONSSProxy) 
            return res;
        else
            throw res;
    }
}

JSONSSProxy._cache = new Array();

JSONSSProxy._getXmlHttpRequest = function() 
{
    try
    {
        if(window.XMLHttpRequest) // Try native XmlHttp
        {
            var xhr = new XMLHttpRequest();
            // some versions of Moz do not support the readyState property and the onreadystate event so we patch it!
            if(xhr.readyState == null) {
                xhr.readyState = 1;
                xhr.addEventListener("load", function() {
                                                 xhr.readyState = 4;
                                                 if(typeof xhr.onreadystatechange == "function")
                                                     xhr.onreadystatechange();
                                             }, false);
            }
            return xhr;
        }
        if(window.ActiveXObject) { // Try IE XmlHttp
            if (JSONSSProxy._getXmlHttpRequest.progid) {
                var xhr = new ActiveXObject(JSONSSProxy._getXmlHttpRequest.progid);
                return xhr;
            } else
                var progids = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];
                for (var i = 0; i < progids.length; i++) {
                     try {
                         var xhr = new ActiveXObject(progids[i]);
                         JSONSSProxy._getXmlHttpRequest.progid = progids[i];
                         return xhr;
                     } catch (ex) {};
                }
        }
    }
    catch (ex) {}
    throw new JSONSSError(501, "No XmlHttp support");
}

JSONSSProxy.prototype._buildInterface = function (intf) {
    if (typeof(intf) != "object")
        throw new JSONSSError(501, "Bad interface object", intf);
    if (!intf.t || ((intf.t != JSONSSProxy.JSONSS_INTERFACE) && (intf.t != JSONSSProxy.JSONSS_SERVICE)))
        throw new JSONSSError(501, "Interface format error", intf);
    if (!intf.i || (intf.i.constructor.toString().indexOf("function Array()") == -1))
        throw new JSONSSError(501, "Interface memebers not found", intf);
    for (var i = 0; i < intf.i.length; i++) {
        if (!intf.i[i].t || !intf.i[i].n || !intf.i[i].u)
            throw new JSONSSError(501, "Bad interface member format", intf[i]);
        switch (intf.i[i].t) {
            case JSONSSProxy.JSONSS_SERVICE:
                var si = intf.i[i].i;
                if (!si || (si.constructor.toString().indexOf("function Array()") == -1))
                    throw new JSONSSError(501, "Service interface memebers not found", intf);
                this._members[intf.i[i].n] = intf.i[i];
                this[intf.i[i].n] = new JSONSSProxy({t: JSONSSProxy.JSONSS_INTERFACE, i: intf.i[i].i}, this.serviceUrl + "/" + intf.i[i].u);
                break;
            case JSONSSProxy.JSONSS_METHOD:
                this._members[intf.i[i].n] = intf.i[i];
                this[intf.i[i].n] = new Function("param", "async", "callback", "return this._invoke(\"" + intf.i[i].n + "\", \"CALL\", param, async, callback);")
                break;
            case JSONSSProxy.JSONSS_PROPERTY:
                this._members[intf.i[i].n] = intf.i[i];
                this["get"+intf.i[i].n] = new Function("async", "callback", "return this._invoke(\"" + intf.i[i].n + "\", \"GET\", null, async, callback);")
                this["set"+intf.i[i].n] = new Function("param", "async", "callback", "return this._invoke(\"" + intf.i[i].n + "\", \"SET\", param, async, callback);")
                break;
            case JSONSSProxy.JSONSS_EVENT:
                this["on"+intf.i[i].n] = null;
                this._members[intf.i[i].n] = intf.i[i];
                break;
            default:
                throw new JSONSSError(501, "Unknown type of interface member", intf.i[i]);
        }
    }
}

JSONSSProxy.prototype._invoke = function (memb, meth, param, async, callback) {
    var post = JSON.stringify(param);
    try {
        var proxy = this;
        var xhr = JSONSSProxy._getXmlHttpRequest();
        xhr.open("POST", this.serviceUrl + "/" + this._members[memb].u, async?true:false);
        xhr.setRequestHeader("JSONSSMethod", meth);
        xhr.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
        this.lastRequestHeaders = "JSONSSMethod: " + meth + "\r\nContent-Type: text/plain; charset=utf-8\r\n";
        this.lastRequest = post;
        if(async) {
            xhr.onreadystatechange = function() {
                if(xhr.readyState == 4)
                    JSONSSProxy._onGotResult(async, callback, xhr, proxy);
            }
        }
        try {
            xhr.send(post);
        } catch (x) {
            if (async)
                if (callback != null)
                    callback(new JSONSSError(501, "Request failed: " + x.toString()));
        }
        if (!async) {
            if (xhr.readyState == 4)
                return JSONSSProxy._onGotResult(async, callback, xhr, proxy);
            else
                throw new JSONSSError(501, "Request failed");
        }
    } catch (x) {
        if (x instanceof JSONSSError) 
            throw x;
        else
            throw new JSONSSError(501, "Request failed: " + x.toString());
    }
}

JSONSSProxy.prototype.ping = function (async) {
    try {
        var proxy = this;
        var xhr = JSONSSProxy._getXmlHttpRequest();
        xhr.open("POST", this.serviceUrl, async?true:false);
        xhr.setRequestHeader("JSONSSMethod", "PING");
        xhr.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
        if(async) {
            xhr.onreadystatechange = function() {
                if(xhr.readyState == 4)
                    JSONSSProxy._onGotResult(async, null, xhr, proxy);
            }
        }
        try {
            xhr.send("");
        } catch (x) {
            if (async)
                if (callback != null)
                    callback(new JSONSSError(501, "Request failed: " + x.toString()));
        }
        if (!async) {
            if (xhr.readyState == 4)
                JSONSSProxy._onGotResult(async, null, xhr, proxy);
            else
                throw new JSONSSError(501, "Request failed");
        }
    } catch (x) {
        if (x instanceof JSONSSError) 
            throw x;
        else
            throw new JSONSSError(501, "Request failed: " + x.toString());
    }
}

JSONSSProxy._onGotResult = function (async, callback, xhr, proxy) {
    var res, ret;
    if (xhr.readyState != 4) {
        res = JSONSSError(501, "Request failed");
    } else {
        try {
            proxy.lastStatusCode = xhr.status;
            proxy.lastResponse = xhr.responseText;
            proxy.lastResponseHeaders = xhr.getAllResponseHeaders();
            res = JSON.parse(xhr.responseText);
        } catch (x) {
            ret = new JSONSSError(501, "Response parsing error: " + x.toString());
        }
    }
    // process return value
    if (!res || typeof(res) != "object" || !res.t)
        ret = new JSONSSError(501, "Bad response format", res);
    if (res.t == JSONSSProxy.JSONSS_FAULT) {
        var c = 501;
        var m = "Unspecified JSONSS fault";
        var d = null;
        if (res.d) {
            if (typeof(res.d.c) == "number")
                c = res.d.c;
            if (typeof(res.d.m) == "string")
                m = res.d.m;
            if (typeof(res.d.d) != "undefined")
                d = res.d.d;
        }
        ret = new JSONSSError(c, m, d);
    } else {
        if (res.t == JSONSSProxy.JSONSS_RESPONSE) {
            if (typeof(res.r) == "undefined")
                ret = null;
            else
                ret = res.r;
        } else
            ret = new JSONSSError(501, "Bad response type", res);
    }
    // process events
    if (res.e && (res.e.constructor.toString().indexOf("function Array()") != -1)) {
        for (var i = 0; i < res.e.length; i++)
            if (res.e[i].n && proxy._members[res.e[i].n] && proxy._members[res.e[i].n].t &&
                (proxy._members[res.e[i].n].t == JSONSSProxy.JSONSS_EVENT)) {
                d = null;
                if (typeof(res.e[i].d) != "undefined")
                    d = res.e[i].d;
                if (typeof(proxy["on"+res.e[i].n]) == "function")
                    proxy["on"+res.e[i].n](d);
            }
    }
    // return result
    if (async) {
        if (callback != null)
            callback(ret);
    } else {
        if (ret instanceof JSONSSError) 
            throw ret;
        else
            return ret;
    }
}

